package cz.pecinovsky.česky.oopnz.utility;
//Příliš žluťoučký kůň úpěl ďábelské ódy


/*******************************************************************************
 *<pre>
 * Předchozí:   Není - toto_je_nově_definovaná_třída
 *              Ttt v projektu Ppp
 * Následující: Prozatím není
 *
 * Projekt  Ppp
 *   + Přidáno
 *   - Odebráno
 *   ~ Změněno
 *</pre>
 */


////////////////////////////////////////////////////////////////////////////////
//%%+  +++++ Konec ignorovaného počátečního textu +++++

import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;


/*******************************************************************************
 * Instance třídy {@code Hodiny} představují ...
 *
 * @author Rudolf PECINOVSKÝ
 * @version 6.03.2017 — 2010-11-08
 */
public class Hodiny
{
//== KONSTANTNÍ ATRIBUTY TŘÍDY =================================================
    
    /** Implicitní velikost periody hodin udávaná v milisekundách. */
    private static final int IMPLICITNÍ_PERIODA = 50; //=> f=20 Hz

    /** Jediná instance hodin, kterou budou všichni sdílet. */
    private static final Hodiny hodiny = new Hodiny();



//== PROMĚNNÉ ATRIBUTY TŘÍDY ===================================================
//== STATICKÝ INICIALIZAČNÍ BLOK - STATICKÝ KONSTRUKTOR ========================
//== KONSTANTNÍ ATRIBUTY INSTANCÍ ==============================================

    /** Přihlášení posluchači hodin, kteří reagují na jejich tiky. */
    private final List<IČasovaný> posluchači = new ArrayList<IČasovaný>();

    /** Časovač, který řídí tikot hodin. */
    private Timer časovač = new Timer("Časovač hodin");



//== PROMĚNNÉ ATRIBUTY INSTANCÍ ================================================

    /** Perioda tikotu hodin. */
    private int perioda = IMPLICITNÍ_PERIODA;

    /** Indikátor běhu hodin. */
    private boolean běží = false;

    /** Úloha, která ve spolupráci s časovačem realizuje tikot hodin. */
    private Úloha úloha = null;

    /** Úvodní akce prováděná před vlastní sérií akcí reagujících na tik -
     *  např. zastavení překreslování plátna. */
    private IČasovaný úvod = null;


    /** Závěrečná akce prováděná po vlastní sérií akcí reagujících na tik -
     *  např. opětně spuštění překreslování plátna. */
    private IČasovaný závěr = null;



//== PŘÍSTUPOVÉ METODY VLASTNOSTÍ TŘÍDY ========================================
//== OSTATNÍ NESOUKROMÉ METODY TŘÍDY ===========================================

//##############################################################################
//== KONSTRUKTORY A TOVÁRNÍ METODY =============================================

    /***************************************************************************
     * Vrátí odkaz na hodiny.
     *
     * @return Odkaz na hodiny
     */
    public static Hodiny getInstance()
    {
        return hodiny;
    }
    

    /***************************************************************************
     * Hodinám stačí prázdný bezparametrický konstruktor.
     */
    private Hodiny()
    {
    }


    
//== ABSTRAKTNÍ METODY =========================================================
//== PŘÍSTUPOVÉ METODY VLASTNOSTÍ INSTANCÍ =====================================

    /***************************************************************************
     * Vrátí aktuálně natavenou periodu hodin.
     *
     * @return Aktuálně nastavená perioda
     */
    public synchronized int getPerioda()
    {
        return this.perioda;
    }


    /***************************************************************************
     * Nastaví novou periodu tikotu hodin.
     *
     * @param milisekund Nastavovaná perioda
     */
    public synchronized void setPerioda(int milisekund)
    {
        if (běží) {
            stop();
            this.perioda = milisekund;
            start();
        } else {
            this.perioda = milisekund;
        }
    }


    /***************************************************************************
     * Vrátí aktuálně natavenou frekvenci tikotu hodin.
     *
     * @return Aktuálně nastavená frekvenci
     */
    public double getFrekvence()
    {
        return 1000.0 / getPerioda();
    }


    /***************************************************************************
     * Nastaví novou frekvenci tikotu hodin. Frekvence se ale nastavuje tak,
     * že se nastaví nejbližší hodnota potřebné periody.
     *
     * @param hertzů Nastavovaná frekvence
     */
    public void setFrekvence(double hertzů)
    {
        setPerioda((int)(1000.0 / hertzů  + 0.5));
    }


    /***************************************************************************
     * Nastaví úvodní akci prováděnou před vlastní sérií akcí 
     * reagujících na tik.
     *
     * @param úvod Objekt realizující úvodní akci
     */
    public synchronized void setÚvod(IČasovaný úvod)
    {
        this.úvod = úvod;
    }


    /***************************************************************************
     * Nastaví závěrečnou akci prováděnou po vlastní sérií akcí 
     * reagujících na tik.
     *
     * @param závěr Objekt realizující závěrečnou akci
     */
    public synchronized void setZávěr(IČasovaný závěr)
    {
        this.závěr = závěr;
    }



//== OSTATNÍ NESOUKROMÉ METODY INSTANCÍ ========================================

    /***************************************************************************
     * Zaregistruje zadaného posluchače a bude mu posílat zprávy
     * o jednotlivých ticích hodin.
     *
     * @param posluchač Registrovaný posluchač
     */
    public void přidej(IČasovaný posluchač)
    {
        if (! posluchači.contains(posluchač)) {
            synchronized(this) {
                posluchači.add(posluchač);
            }
        }
    }


    /***************************************************************************
     * Odregistruje zadaného posluchače a přestane mu posílat zprávy
     * o jednotlivých ticích hodin.
     *
     * @param posluchač Registrovaný posluchač
     */
    public void odeber(IČasovaný posluchač)
    {
        if (posluchači.contains(posluchač)) {
            synchronized (this) {
                posluchači.remove(posluchač);
//                //Otázka, zda pozastavovat hodiny, když už není komu tikat
//                if (posluchači.size() == 0) {
//                    stop();
//                }
            }
        }
    }


    /***************************************************************************
     * Spustí hodiny se zadanou periodou.
     */
    public synchronized void start()
    {
        if (běží) { return; }

        úloha = new Úloha();
        časovač.scheduleAtFixedRate(úloha, 0, perioda);
        this.běží = true;
    }


    /***************************************************************************
     * Zastaví hodiny, takže přestanou tikat.
     */
    public synchronized void stop()
    {
        úloha.cancel();
        this.běží = false;
    }



//== SOUKROMÉ A POMOCNÉ METODY TŘÍDY ===========================================
//== SOUKROMÉ A POMOCNÉ METODY INSTANCÍ ========================================
//== INTERNÍ DATOVÉ TYPY =======================================================
    
    /***************************************************************************
     * Instance třídy {@code Úloha} jsou úlohy spouštěné časovačem
     * v pravidelných intervalech a jejich úkolem je oznámit všem
     * registrovaným posluchačím, že hodiny tikly.
     */
    private class Úloha extends TimerTask
    {
        @Override
        public void run()
        {
            synchronized(hodiny) {
                if (úvod != null) { úvod.tik(); }
                for( IČasovaný posluchač : posluchači) {
                    posluchač.tik();
                }
                if (závěr != null) { závěr.tik(); }
            }
        }
    }
    
    
    
//== TESTY A METODA MAIN =======================================================
//
//    private static class Testovací implements IČasovaný {
//        static int počet  = 0;
//        final  int pořadí = ++počet;
//
//        long minule = System.currentTimeMillis();
//        int  tik    = 0;
//
//        @Override
//        public void tik()
//        {
//            long nyní = System.currentTimeMillis();
//            long doba = nyní - minule;
//            System.out.printf("T%1d - %3d - %3d.%03d\n",
//                              pořadí, ++tik, doba/1000, doba%1000);
//            minule = nyní;
//        }
//
//    }
//
//    /***************************************************************************
//     * Testovací metoda.
//     */
//    public static void test()
//    {
//        Testovací t = new Testovací();
//        hodiny.setFrekvence(1.0);
//        hodiny.přidej(t);
//        hodiny.start();
//        System.out.println("Budu čekat 5 sekund - frekvence 1");
//        IO.čekej(5000);
//        System.out.println("PO 5 sekundách - přidávám druhý objekt");
//        hodiny.přidej(new Testovací());
//        IO.čekej(3000);
//        System.out.println("Po dalších 3 sekundách - nyní frekvence 3");
//        hodiny.setFrekvence(3);
//        IO.čekej(3000);
//        System.out.println("Po dalších 3 sekundách - odebírám první objekt");
//        hodiny.odeber(t);
//        IO.čekej(3000);
//        System.out.println("Po dalších 3 sekundách");
//        hodiny.stop();
//    }
//    /** @param args Parametry příkazového řádku - nepoužívané. */
//    public static void main( String[] args )  {  test();  }
}
